package com.humanminoritygame.model;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.Sort;
import org.hibernate.annotations.SortType;
import org.hibernate.annotations.Type;
import org.joda.time.DateTime;

@Entity
@Table(uniqueConstraints = {@UniqueConstraint(columnNames={"startTime","categoryId"})})
public class Room extends PersistentEntity implements Comparable<Room> {
	
  public static enum State {
    OPEN, EMPTY, CLOSED;
  };
  public static final int MAXROUNDS = 50;
  public static final int MAXAVAILABLEROUNDS = (int)Math.round(0.8*MAXROUNDS);
  private static final int MINACTIVESUBSCRIPTIONS = 3;
  
  @Type(type="org.joda.time.contrib.hibernate.PersistentDateTime")
  private DateTime startTime;
  
  @ManyToOne
  @JoinColumn(name="categoryId")
  private Category category;

  @OneToMany( cascade = CascadeType.ALL, mappedBy="room")
  @Sort(type=SortType.NATURAL)
  @Cascade(org.hibernate.annotations.CascadeType.ALL)
  private SortedSet<Round> rounds = new TreeSet<Round>();
  
  @Enumerated(EnumType.STRING)
  private State state = State.EMPTY;
  private int activeSubscriptions = 0;
  
  @OneToMany(cascade = CascadeType.ALL, mappedBy="room")
  @Sort(type=SortType.NATURAL)
  @Cascade(org.hibernate.annotations.CascadeType.ALL)
  private SortedSet<Subscription> subscriptions = new TreeSet<Subscription>();

  //esto es para saber el valor de la "accion",el valor acumulado de lo qe fue saliendo en las rondas
  private int acumulatedValue;
  
  Room() {
  }
  
  public Room(Category category){
    super();
    this.category = category;
    this.startTime = new DateTime();//now
    this.setAcumulatedValue(0);
  }
  
  public boolean lastRoundExpired() {
    if(rounds.isEmpty()){
      return true;
    }
    return rounds.last().hasElapsed();
  }

  public Round addRound(){
    Round round = new Round(this);

    if (getState() != State.OPEN) {
      throw new RuntimeException("The room is not open, no rounds can be added.");
    }
    if(rounds.size() >= MAXROUNDS){
      state = State.CLOSED;
      throw new RuntimeException("The room has already played the maximum amount of rounds.");
    }
    rounds.add(round);
    return round;
  }

  public State getState() {
    return state;
  }

  public void addSubscription(Subscription subscription) {
    if (state == State.CLOSED) {
      throw new RuntimeException("The room is closed");
    }
    activeSubscriptions++;
    if(activeSubscriptions >= MINACTIVESUBSCRIPTIONS){
      state = State.OPEN;
    }
    subscriptions.add(subscription);
  }
  
  public void removeSubscription(Subscription s){
    if(state == State.CLOSED){
      throw new RuntimeException("The room is closed");
    }
    activeSubscriptions--;
    if(activeSubscriptions < MINACTIVESUBSCRIPTIONS){
      state = State.CLOSED;
    }
  }

  @Override
  public int compareTo(Room o) {
    if(equals(o)){
      return 0;
    }
    
    int result = category.compareTo(o.getCategory());
    if(result == 0){
      result = startTime.compareTo(o.startTime);
    }
    return result;
  }

  public DateTime getStartTime() {
    return this.startTime;
  }
  
  public String getNiceTime(){
    return startTime.getDayOfMonth() + "/" + startTime.getMonthOfYear() + "/" + startTime.getYear() + " " 
      + startTime.getHourOfDay() + ":" + startTime.getMinuteOfHour() ;
  }
  
  public void close(){
    state = State.CLOSED;
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + (int)category.getFrequency();
    result = prime * result + ((startTime == null) ? 0 : (int)startTime.getMillis());
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    return super.equals(obj);
  }

  public boolean isOpen(){
    return state == State.OPEN;
  }

  public Set<Subscription> getSubscriptions() {
    return subscriptions;
  }

  public SortedSet<Subscription> getActiveSubscriptions() {
    SortedSet<Subscription> resp = new TreeSet<Subscription>();
    for(Subscription s : subscriptions){
      if(s.getState()==Subscription.State.ACTIVE){
        resp.add(s);
      }
    }
    return resp;
  }

  public Round getLastRound() {
    if(rounds.isEmpty()){
      return null;
    }
    return rounds.last();
  }

  public Round getPreviousLastRound() {
    if(rounds.isEmpty()){
      return null;
    }
    
    
    return rounds.last();
  }

  public SortedSet<Round> getLastWindowRounds(int w) {
    int position=Math.max(0,rounds.size()-w);
    Round from = null;
    for(Round r : rounds){
      if(position == 0){
        from = r;
      }
      position--;
    }

    if(from == null){
      return new TreeSet<Round>();
    } else {
      return rounds.tailSet(from);
    }
  }

  public boolean isAvailableForSubscription() {
    if(state == State.EMPTY ){
      return true;
    }
    if(state == State.OPEN && MAXAVAILABLEROUNDS> this.rounds.size()){
      return true;
    }
    return false;
  }

  public Category getCategory() {
    return category;
  }

  public SortedSet<Round> getRounds() {
    return rounds;
  }

  public int getQtyActiveSubscriptions() {
    return activeSubscriptions;
  }
  
  public boolean isSubscribed(User user){
    for(Subscription s: subscriptions ){
      if(s.getUser().equals(user)){
        return true;
      }
    }
    return false;
  }

  public boolean isEmpty() {
    return state == State.EMPTY;
  }

  public void setAcumulatedValue(int acumulatedValue) {
    this.acumulatedValue = acumulatedValue;
  }

  public int getAcumulatedValue() {
    return acumulatedValue;
  }

  public boolean isClosed() {
    return state == State.CLOSED;
  }

  public int getRoundQty() {
    return rounds.size();
  }
  
  public int getWorstScore(){
    int min= 0;
    for(Subscription s: subscriptions){
      min = Math.min(min, s.getScore());
    }
    return min;
  }
  
  
  /**
   * 
   * @return the remaining time in seconds for the room to close
   */
  public int getRemainingTime(){
    if(isClosed()){
      return 0;
    }
    return (Room.MAXROUNDS - rounds.size() +1)*category.getFrequency();
  }
  
  /**
   * 
   * @return the remaining time in seconds for the room to admit new subscriptions
   */
  public int getRemainingTimeForSubscription(){
    return (Room.MAXAVAILABLEROUNDS - rounds.size())*category.getFrequency();
  }
}
